#ifndef HANDY_STUFF_HPP
#define HANDY_STUFF_HPP
#include <chrono>
#include <string>
#include <iostream>
#include <limits>
#include <map>
#include <type_traits>
#include "basetypes.hpp"

namespace utils
{
//===============================================================================================
// Struct
//===============================================================================================

    /************************************************************************
        data_array_struct
            A base structure for structures to be read from files, such as 
            headers, and any constant size blocks of data !
    ************************************************************************/
    struct data_array_struct
    {
        virtual ~data_array_struct(){}
        virtual unsigned int size()const=0;

        //The method expects that the interators can be incremented at least as many times as "size()" returns !
        virtual std::vector<uint8_t>::iterator       WriteToContainer(  std::vector<uint8_t>::iterator       itwriteto )const = 0;

        //The method expects that the interators can be incremented at least as many times as "size()" returns !
        virtual std::vector<uint8_t>::const_iterator ReadFromContainer( std::vector<uint8_t>::const_iterator itReadfrom )     = 0;
    };


    /************************************************************************
        MrChronometer
            A small utility RAII class that that tells the time elapsed 
            between its construction and destruction.
    ************************************************************************/
    struct MrChronometer
    {

        MrChronometer( const std::string name = "*", std::ostream & messageoutput = std::cout );
        ~MrChronometer();

        std::chrono::steady_clock::time_point _start;
        std::string                           _name;
        std::ostream                         &_output;
    };

    /************************************************************************
        optionparsing_t
            Struct for holding details on how to parse arguments from the commandline.
    ************************************************************************/
    struct optionparsing_t
    {
        std::string  optionsymbol;      //Symbol to look for, only if its an option/switch, excluding the '-' !
        unsigned int nbvalues;          //Nb of values to consider part of this arg option from the following whitespace bounded arguments.
        std::string  description;       //Short description
    };

//===============================================================================================
// Classes
//===============================================================================================

    /************************************************************************
        CArgsParser
            A small class to parse parameters passed at the command line.
    ************************************************************************/
    class CArgsParser
    {
    public:
        CArgsParser( const char * argv[], int argc  );
        CArgsParser( std::vector<optionparsing_t> optionlist, const char * argv[], int argc );
        
        //Return the reading pos to the beginning of the Parameter list (Affects only parameter reading, not option reading)
        void ResetReadPos();

        // ----- Arguments -----
        //Return the current whitespace bounded string of character that isn't a switch/option
        // If no more parameters, returns an empty string!
        std::string getCurrentParam();

        //Return the next whitespace bounded string of character that isn't a switch/option
        // If no more parameters, returns an empty string!
        std::string getNextParam();

        //Skip the current parameter whithout attempting to parse it.
        // Useful for skipping the program's name in the list of arguments.
        void skipCurrentParam();

        //Returns whether the whole argument array has been scanned for arguments
        bool hasReachedTheEndOfArgList()const;

        // ----- Options -----
        //Takes the index of the option from the option list passed at construction, if its the case!
        // -> returns a vector of strings.
        //      -> is empty if option not found
        //      -> if found 
        //          -> first entry is symbol of option/switch
        //          -> additional entries are the parameters for this particular options. As specified in the optionlist at construction.
        std::vector< std::string > getOption( std::vector<optionparsing_t>::size_type optionindex );

        //Returns a list of all the found options considering the params passed at construction.
        // -> The first entry in each std::vector<std::string> is the symbol of the option that was found
        std::vector< std::vector<std::string> > getAllFoundOptions();

        //Appends all the parameters preceded with a "+". It removes the "+" from the appended elements.
        //It appends the results to the vector specified, using "push_back".
        // Returns false if there was nothing to add.
        bool appendAllAdditionalInputParams( std::vector<std::string> & out_vtoappendto );

    private:
        unsigned int getNbArgsForOption( const std::string & option );

        std::vector<std::string>                 m_rawargs;
        std::vector<std::string>::const_iterator m_rawcurarg;
        std::vector<optionparsing_t>             m_optionlist;

        static const std::string SWITCH_SYMBOL;
        static const std::string ADDITIONAL_INPUT_PARAM_SYMBOL;
    };

//===============================================================================================
// Function
//===============================================================================================

    /************************************************************************
        SimpleHandleException
            A function to avoid having to rewrite a thousand time the same 2 
            lines of code in case of exception.

            Simply write the exception's "what()" function output to "cerr", 
            and triggers an assert in debug.
    ************************************************************************/
    void SimpleHandleException( std::exception & e );

    /************************************************************************
        WriteIntToByteVector
            Tool to write integer values into a byte vector!
            Returns the new pos of the iterator after the operation.
    ************************************************************************/
    template<class T, class _outit>
        _outit WriteIntToByteVector( T val, _outit itout, bool basLittleEndian = true )
    {
        static_assert( std::numeric_limits<T>::is_integer, "WriteIntToByteVector() : Used another types than an integer!" );

        auto lambdaShiftAssign = [&val]( unsigned int shiftamt )->uint8_t
        {
            T tempshift = 0;
            tempshift = ( val >> (shiftamt * 8) ) & 0xFF;
            return tempshift & 0xFF;
        };

        if( basLittleEndian )
        {
            for( unsigned int i = 0; i < sizeof(T); ++i, ++itout )
                (*itout) = lambdaShiftAssign( i );
            //{
            //    T tempshift = 0;
            //    tempshift = ( val >> (i * 8) ) & 0xFF;
            //    (*itout) = static_cast<uint8_t>( tempshift & 0xFF );
            //}
        }
        else
        {
            for( unsigned int i = (sizeof(T)-1); i >= 0 ; --i, ++itout )
                (*itout) = lambdaShiftAssign( i );
            //{
            //    T tempshift = 0;
            //    tempshift = ( val >> (i * 8) ) & 0xFF;
            //    (*itout) = static_cast<uint8_t>( tempshift & 0xFF );
            //}
        }

        return itout;
    }

    /************************************************************************
        ReadIntFromByteVector
            Tool to read integer values from a byte vector!
            ** The iterator's passed as input, has its position changed !!
    ************************************************************************/
    template<class T, class _init> T ReadIntFromByteVector( _init & itin, bool basLittleEndian = true ) //#TODO : Need to make sure that the iterator is really incremented!
    {
        static_assert( std::numeric_limits<T>::is_integer, "ReadIntFromByteVector() : Error, used another types than an integer!" );
        T out_val = 0;

        auto lambdaShiftBitOr = [&itin, &out_val]( unsigned int shiftamt )
        {
            T tmp = (*itin);
            out_val |= ( tmp << (shiftamt * 8) ) & ( 0xFF << (shiftamt*8) );
        };

        if( basLittleEndian )
        {
            for( unsigned int i = 0; i < sizeof(T); ++i, ++itin )
                lambdaShiftBitOr(i);
            //{
            //    T tmp = (*itin);
            //    out_val |= ( tmp << (i * 8) ) & ( 0xFF << (i*8) );
            //}
        }
        else
        {
            for( unsigned int i = (sizeof(T)-1); i >= 0; --i, ++itin )
                lambdaShiftBitOr(i);
        }

        return out_val;
    }

    /************************************************************************
        ChangeValueOfASingleByte
            Allows to change the value of a single byte in a larger type! 
            As if it was an array.
    ************************************************************************/
    template< class T >
        T ChangeValueOfASingleByte( T containinginteger, uint8_t newvalue, uint32_t byteoffset  )
        {
            T mask = 0xFFu,
              val  = newvalue;

            mask = (mask << (sizeof(T) - byteoffset) * 8); //Am I over cautious ?
            val  = (val  << (sizeof(T) - byteoffset) * 8);

            return ( (val & mask) | containinginteger );
        }

// ---------------------------------------------------------------
// File related stuff 
// ---------------------------------------------------------------

    //**TODO** : Move those at some point!

    /************************************************************************
        DoCreateDirectory
            Creates a new subdirectory at the path specified
    ************************************************************************/
    bool DoCreateDirectory( const std::string & path );

    /************************************************************************
        isFolder
            Returns true if the path specified is a directory and exists.
    ************************************************************************/
    bool isFolder( const std::string & inputpath );

    /************************************************************************
        isFile
            Returns true if the path specified is a file and exists.
    ************************************************************************/
    bool isFile( const std::string & inputpath );

    /************************************************************************
        pathExists
            Returns true if the path specified is valid and is either a file 
            or directory.
    ************************************************************************/
    bool pathExists( const std::string & inputpath );

    /************************************************************************
        AppendTraillingSlashIfNotThere
            Appends a trailing slash to a path if its not there in the first 
            place ! Move constructor makes this fast !
    ************************************************************************/
    std::string AppendTraillingSlashIfNotThere( const std::string &path );

    /************************************************************************
        ReadFileToByteVector
            Read the file content straight into a byte vector, with no 
            processing at all. Takes the path to the file and a vector to 
            put the data as parameters.
    ************************************************************************/
    void ReadFileToByteVector( const std::string & path, std::vector<uint8_t> & out_filedata );

    /************************************************************************
        WriteByteVectorToFile
            Write the byte vector content straight into a file, with no 
            processing at all. Takes the path to the file and a vector with 
            the data as parameters.
    ************************************************************************/
    void WriteByteVectorToFile( const std::string & path, const std::vector<uint8_t> & filedata );
};

#endif 